#include <sys/types.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <pwd.h>
#include <grp.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>

#ifdef FREEBSD4
#include "my_semaphore.h"
#else
#include <semaphore.h>
#endif

#include "queued.h"
#include "log.h"
#include "rqueue.h"
#include "wqueue.h"
#include "method.h"
#include "command.h"

static sem_t *onexit = NULL;

static char pid[32];

pthread_t pth_wqueue;
pthread_t pth_rqueue;

static int daemonize()
{
	sigset_t set;
	sigfillset(&set);

	//Dont't block signals which would cause us to dump core.
	sigdelset(&set, SIGQUIT);
	sigdelset(&set, SIGILL );
	sigdelset(&set, SIGTRAP);
	sigdelset(&set, SIGABRT);
	sigdelset(&set, SIGFPE );
	sigdelset(&set, SIGSEGV);
	sigdelset(&set, SIGBUS );

	//Don't block SIGCHLD and SIGTERM
	sigdelset(&set, SIGCHLD);

	//No kill...
	sigaddset(&set, SIGTERM);

	if(-1 == sigprocmask(SIG_BLOCK, &set, NULL))
		return -1;

#ifndef NOT_HAVE_DAEMON
	if(-1 == daemon(0, 0))
		return -1;
#else
	if(0 != fork()) exit(0);

	if(-1 == setsid()) exit(0);

	if(0 != fork()) exit(0);

	if(0 != chdir("/")) exit(0);
#endif

	return 1;
}

int switch_user()
{
	struct passwd *pw;
	struct group  *gr;
	const char *uid, *gid;

	uid = INI_GetValue(cfg, "global", "uid");
	gid = INI_GetValue(cfg, "global", "gid");
	pw = getpwnam(uid);
	gr = getgrnam(gid);
	if(!pw || !gr)
	{
		fprintf(stderr, "No such user or group %s:%s failed.\n", uid, gid);
		return -1;
	}

	if(0 != setgid(gr->gr_gid) ||
	   0 != setuid(pw->pw_uid))
	{
		pw = getpwuid(getuid());
		gr = getgrgid(pw->pw_gid);
		fprintf(stderr, "Authorization failure, from %s:%s to %s:%s.\n",
			pw->pw_name, gr->gr_name, uid, gid);
		return -1;
	}

	return 1;
}

int process_mutex_lock()
{
again:
#ifndef FREEBSD4
	onexit = sem_open (pid, O_CREAT|O_EXCL|O_RDWR, 0644, 0);
#else
	onexit = qsem_open(pid, O_CREAT|O_EXCL|O_RDWR, 0644, 0);
#endif

	if(onexit == SEM_FAILED)
	{
		if(errno == EINTR) goto again;
#ifdef DEBUG
		fprintf(stderr, "sem_open failed! (%s)\n", strerror(errno));
#endif
		return -1;
	}

	return 1;
}

int process_mutex_unlock()
{
#ifndef FREEBSD4
	while(0 != sem_close(onexit))
	{
		if(errno == EINTR) continue;
#ifdef DEBUG
		fprintf(stderr, "sem_close failed! (%s)\n", strerror(errno));
#endif
		return -1;
	}

	while(0 != sem_unlink(pid))
	{
		if(errno == EINTR) continue;
#ifdef DEBUG
		fprintf(stderr, "sem unlink failed! (%s)\n", strerror(errno));
#endif
		return -1;
	}
#else
	while(0 != qsem_close(onexit))
	{
		if(errno == EINTR) continue;
#ifdef DEBUG
		fprintf(stderr, "sem_close failed! (%s)\n", strerror(errno));
#endif
		return -1;
	}

	while(0 != qsem_unlink(pid))
	{
		if(errno == EINTR) continue;
#ifdef DEBUG
		fprintf(stderr, "sem unlink failed! (%s)\n", strerror(errno));
#endif
		return -1;
	}

#endif
	return 1;
}

void queue_init(const char* config, int is_daemon, int is_debug)
{
	if(-1 == queue_init_conf(config))
		exit(1);

	// Switch user for security.
	if(-1 == switch_user())
		exit(1);
		
	if(-1 == queue_status_init())
	{
		fprintf(stderr, "httpqueue initialize failed.\n");
		exit(1);
	}
	
	if(!is_daemon)
		return;

	/* only for daemon process. */
	strcpy(pid, INI_GetValue(cfg, "global", "pid"));
	if(-1 == process_mutex_lock())
	{
		fprintf(stderr, "other httpqueue running or sem open error, run qclean first.\n\n");
		exit(1);
	}

#ifndef DEBUG
	if(is_debug == 0 &&  0 > daemonize())
	{
		fprintf(stderr, "create daemon failure.\n");
		logger(PRIORITY_ERROR, "Create daemon failure at %d in '%s'.",
			__LINE__, __FILE__);
		process_mutex_unlock();
		exit(1);
	}
#endif
}

void queue_fini(int is_daemon)
{
	queue_status_fini(is_daemon);
	if(is_daemon) {
		process_mutex_unlock();
	}
}

void queue_start()
{
	pthread_t pth_listen;
	if(0 != pthread_create(&pth_wqueue, NULL, (void*)wqueue_start, NULL))
	{
		logger(PRIORITY_ERROR, "pthread_create pth_wqueue error at %d in '%s'.",
		__LINE__, __FILE__);

		fprintf(stderr, "pthread_create pth_wqueue error at %d in '%s'.",
		__LINE__, __FILE__);
		queue_fini(1);
		exit(1);
	}

#ifdef DEBUG
	fprintf(stderr, "wqueue started\n");
#endif

	// rqueue start ....
	if(0 != pthread_create(&pth_rqueue, NULL, (void*)rqueue_start, NULL))
	{
		logger(PRIORITY_ERROR, "pthread_create pth_rqueue error at %d in '%s'.",
			__LINE__, __FILE__);

		fprintf(stderr, "pthread_create pth_rqueue error at %d in '%s'.",
			__LINE__, __FILE__);
		queue_fini(1);
		exit(1);
	}

#ifdef DEBUG
	fprintf(stderr, "rqueue started\n");
#endif
	if(0 != pthread_create(&pth_listen, NULL, (void*)console, NULL))
	{
		logger(PRIORITY_ERROR, "pthread_create pth_wqueue error at %d in '%s'.",
			__LINE__, __FILE__);

		fprintf(stderr, "pthread_create pth_wqueue error at %d in '%s'.",
			__LINE__, __FILE__);
		queue_fini(1);
		exit(1);
	}

	pthread_join(pth_listen, NULL);
}

void queue_stop()
{
	rqueue_stop();
	pthread_join(pth_rqueue, NULL);
	wqueue_stop();
	pthread_join(pth_wqueue, NULL);
	queue_fini(1);
}

void console()
{
again:
#ifndef FREEBSD4
	if(0 != sem_wait (onexit))
#else
	if(0 != qsem_wait(onexit))
#endif
	{
		if(errno == EINTR) goto again;
#ifdef DEBUG
		fprintf(stderr, "sem_wait failed! (%s)\n", strerror(errno));
#endif
		logger(PRIORITY_ERROR, "sem_wait failed in function console! at %d in '%s'.",
			__LINE__, __FILE__);
	}

#ifdef DEBUG
	fprintf(stderr, "sem post get\n");
#endif

	if(control->cmd != CMD_STOP)
	{		
		if(-1 == cmd_call_back())
		{
			logger(PRIORITY_ERROR, "cmd_call_back failed at %d in '%s'.",
			__LINE__, __FILE__);
		}
		goto again;
	}
	
#ifdef DEBUG
	fprintf(stderr, "httpqueue stop....\n");
#endif
	queue_stop();
}

void command_pal(Command cmd)
{
	const char *pid;
	int count = 0;
	pid = INI_GetValue(cfg, "global", "pid");
	
again:
#ifndef FREEBSD4
	onexit = sem_open (pid, O_RDWR, 0644, 0);
#else
	onexit = qsem_open(pid, O_RDWR, 0644, 0);
#endif
	if(onexit == SEM_FAILED)
	{
		if(errno == EINTR) goto again;
		fprintf(stderr, "none httpqueue daemon running. (%s)\n", strerror(errno));
		exit(1);
	}

	control->cmd = cmd;
	if(-1 == msync(control, sizeof(control), MS_SYNC))
	{
		fprintf(stderr, "memmory error. (%s)", strerror(errno));
		exit(1);
	}

post_again:
#ifndef FREEBSD4
	if(0 != sem_post (onexit))
#else
	if(0 != qsem_post(onexit))
#endif
	{
		if(errno == EINTR) goto post_again;
		fprintf(stderr, "sem_post failed! (%s)\n", strerror(errno));
	}

	if(cmd == CMD_STOP) return;
	
	while(control->cmd != CMD_NULL) 
	{
#ifdef DEBUG
		fprintf(stderr, ".");
#endif
		usleep(100000);
		if(count++ == 50)
		{
			count = 0;
			fprintf(stderr, "unknow error!");
			return;
		}
	}
	
#ifdef DEBUG
	fprintf(stderr, "\n");
#endif
	printf("%s\n", control->detail);
	memset(control, 0x0, sizeof(queue_control));
}

